/**
 * mLibLin3.js
 * Created: April 2013
 * Source link: http://code.google.com/p/code-depository/source/browse/
 *
 * By Suresh Nalluri,
 * License: The MIT License (MIT)
 *          See LICENSE details
 *
 *
 *	Todo:
 *			transform TBD
 *
 * Requires:
 *	mLibFactory.js
 *	mLibIncludes.js
 *	mLibVec3.js
 *	mLibLineSegment.js
 *	mLIB.Quaternion,
 *  mLIB.Mat44;
 *
 *
 * 3D Line Class
 * A Line object is composed of an anchor point (or origin) and a normalised Direction vector.
 * A line segment extends in both directions from the anchor point.
 * Note the components themselves are objects.
 *
 *
 * note this constructor pattern allows us to create objects in 2 ways:
 * 1. var o = mLib.Lin3( 0 or more params );      //OR
 * 2. var o = new mLib.Lin3( 0 or more params );
 *
 * NOTE: var o = [new] mLIB.Lin3(<no params>); // will return default line composed of the
 * unit vector in direction i (1,0,0), and the Origin (0,0,0) as the starting point.
 *
 *
 * added benefit of this pattern is that we get a copy constructor by default!!
 * eg. var newLine = mLIB.Lin3(existingLine);
 * so no need to have an explicit copy method.
 *
 * Usage:
 *		var l = [new] mLIB.Lin3([origin, direction | lineSeg | <noargs> ]);
 * 
 *-------------------------------------------------------
 **/
 

if (typeof mLIB == 'undefined') function mLIB() {};

( function(module) {
	var Lin3 = module.Lin3,
        Mat44 = module.Mat44,
        Vec3 = module.Vec3,
        Point3 = module.Point3;
    
	module.Lin3 = Factory.obj( {
		init: function() {

			if ( !arguments.length ) {
				// default if no params present
				this.origin = module.Point3(module.Vec3.origin),
			  	this.direction = module.Vec3(module.Vec3.xAxis);

			} else if (arguments[0].isClass(module.Point3) && arguments[1].isClass(module.Vec3) ) {
				// called with params of origin and direction vector, ie. new module.Lin3(o, d)
				this.origin = module.Point3(arguments[0]);
				this.direction = module.Vec3(arguments[1]).normalise();

			} else if ( arguments[0].isClass(module.Lin3Seg) ) {
				// called with a param = an existing line segment, ie. new module.Lin3(lineSeg)
				this.origin = module.Point3(arguments[0].origin);
				this.direction = module.Vec3(arguments[0].direction).normalise();
				
			} else if ( arguments[0].isClass(module.Lin3) ) {
				// called with a param = an existing Line (ie. a plain copy!);
				this.origin = module.Point3(arguments[0].origin);
				this.direction = module.Vec3(arguments[0].direction);
				
			} else {
				// default if params type unknown
				this.origin = module.Point3(module.Vec3.origin),
			  	this.direction = module.Vec3(module.Vec3.xAxis);
			}
			  
		}, // init()

        // transforms or maps the line into a new space
		// requires the Quaternion and Mat44 libs
        transform: function(scale, rQuat, translateVec) {
            var direction, origin,
                transOp;
            
            // form the transformation matrix from the quaternion and the scale factor
            // note this is probably not the most efficient way but at least it doesn't
            // break any oo rules.
            transOp = new Mat44(rQuat).multiply(Mat44().scaling(Vec3(scale, scale, scale));
            
            
            /** Alternative more efficient way, but breaking the encapsulation rule
             *  ie. do the matrix multiplication and set  the new values inline
             *
             *   transOp = new Mat44(rQuat)
             *   var T = transOp.$rawArray();
             *
             *   // apply the scaling factor to the matrix
             *   T[0]  *= scale;    // T(0,0) *= scale;
             *   T[1]  *= scale;    // T(1,0) *= scale;
             *   T[2]  *= scale;    // T(2,0) *= scale;
             *   T[4]  *= scale;    // T(0,1) *= scale;
             *   T[5]  *= scale;    // T(1,1) *= scale;
             *   T[6]  *= scale;    // T(2,1) *= scale;
             *   T[8]  *= scale;    // T(0,2) *= scale;
             *   T[9]  *= scale;    // T(1,2) *= scale;
             *   T[10] *= scale;    // T(2,2) *= scale;
             **/
                                    
            // transform the direction;
            direction = transOp.Transform(this.direction).normalise();
            
            // apply the translation vector to the matrix
            // again note: not the most efficient way of doing it, but no oo rules broken
            transOP.addTranslation(translateVec);
            
            /** Alternative more efficient way, but breaking the encapsulation rule
             *
             *   T[12] = translateVec.x;   // T(0,3)
             *   T[13] = translateVec.y;   // T(1,3)
             *   T[14] = translateVec.z;   // T(2,3)
             **/
            
            // transform the origin
            origin = transOp.Transform(this.origin);
            
            return Lin3(origin, direction);
            
        },

        // get closest point on line to a point p
        // use of the projection formula
        ClosestPoint: function(p) {
            var w = p.Minus(this.origin),
				// vsq = this.direction.dot(this.direction), //should always be 1 since direction is a unit vector
                prj = w.dot(this.direction);
                
				// Lo + (prj/vsq)*Ld, but since vsq=1, we simplify to Lo +prj*Ld
                return this.direction.ScaleUp(prj).plus(this.origin);
        },
		
        // get closest points between this line l1 and the line l2
        ClosestPoints: function(l2) {
            var w0 = this.origin.Minus(l2.origin),
				a = this.direction.dot(l2.direction),	// u.v
				// b = l2.direction.dot(l2.direction) should always be 1 since L2d is the unit direction vector
				// c = this.direction.dot(this.direction)should always be 1 since L1d is the unit direction vector
				d = w0.dot(l2.direction),			// w0.v
				e = w0.dot(this.direction),			// w0.u
				denom = a*a - 1.0;	// denom = a*a-b*c; but since  b=c=1, simplify to denom = a*a-1;
				
            if ( module.Includes.isZero(denom) ) {
				// s = 0.0
				// t= d/b, but since b=1, we simplify to t = d
				return {
                	Pc: module.Vec3(this.origin),					// L1o
                	Qc: l2.direction.ScaleUp(d).plus(l2.origin)		// L2o + t*L2d.
				};
            } else
            {
				// s = ((e*b-a*d)/denom), but since b=1; we simplify to s = ((e-a*d)/denom)
				// t = ((e*a-d*c)/denom), but since c=1; we simplify to t = ((e*a-d)/denom)
				return {
                	Pc: this.direction.ScaleUp((e-a*d)/denom).plus(this.origin),// L1o + s*L1d
                	Qc: l2.direction.ScaleUp((e*a-d)/denom).plus(l2.origin)		// L2o + t*L2d
				};
            }
        },
		
		distanceSquared: function(e) {
			if ( e.isClass(module.Lin3) ) {
				// between 2 lines
				return disSqLinLin(this, e);
			}
			else if ( e.isClass(module.Point3) ) {
				// between a line and a point
				return disSqLinPoint(this, e);
			}
			
			// unknown parameter types
			return null;
		},
        
        // equality tests
        isEqual: function(l) {        
            return ( this.origin.isEqual(l.origin) && this.direction.isEqual(l.direction) );
        },
        
        isNotEqual: function(l) {
            return !( this.origin.isEqual(l.origin) && this.direction.isEqual(l.direction) );
        },
        
		// zero the elements of self that are close to zero.
        roundToZero: function() {
            this.origin.roundToZero();
            this.direction.roundToZero();
        },
        
        // accessor and setter methods
        getOrigin: function() {
            return this.origin;
        },

        getDirection: function() {
            return this.direction;
        },
        
        get: function(out) {
            // we return the object in the parameter object
            out.Origin = this.origin;
            out.Direction = this.direction;
            
            return this;  // let's return this anyway.. for chaining purposes
        },
        
        // set origin and direction
        set: function(origin, direction) {
            this.origin = module.Point3(origin);
            this.direction = module.Vec3(direction).normalise();
			return this;
        },
        
        // debug
        log: function() {
			return ['{ o:', this.origin.log(), ', d:', this.direction.log(), '}'].join('');
        },
		
		toArray: function() {
			return [this.origin.toArray(), this.direction.toArray()].join('');
		}

	} ); // module.Lin3 = Factory.obj
		
	
	// internal helper functions ----------------------------------------
	
	// distance squared between 2 lines
	function disSqLinLin(l1, l2) {
		var wc,
			w0 = l1.origin.Minus(l2.origin),
			a = l1.direction.dot(l2.direction),	// u.v
			// b = l2.direction.dot(l2.direction) should always be 1 since L2d is the unit direction vector
			// c = this.direction.dot(this.direction)should always be 1 since L1d is the unit direction vector
			d = w0.dot(l2.direction),			// w0.v
			e = w0.dot(l1.direction),			// w0.u
			denom = a*a - 1.0;		// denom = a*a-b*c; but since  b=c=1, simplify to denom = a*a-1;
		
		if ( module.Includes.isZero(denom) ) {
			// the 2 lines are parallel
			// t= d/b, but since b=1, we simplify to t = d
			// wc = w0 - t*L2d
			//
			// note use of ScaleUp since we don't want to clobber the parameters.. as they 
			// may be in use elsewhere !  ScaleUp returns new objects.
			
			wc = w0.minus( l2.direction.ScaleUp(d) );
		} else
		{
			// s = ((e*b-a*d)/denom), but since b=1; we simplify to s = ((e-a*d)/denom)
			// t = ((e*a-d*c)/denom), but since c=1; we simplify to t = ((e*a-d)/denom)
			// wc = w0 + s*L1d - t*L2d
			
			wc = w0.plus(l1.direction.ScaleUp((e-a*d)/denom).
				minus(l2.direction.ScaleUp((e*a-d)/denom)));
		}
		
		return wc.dot(wc);
		
	} //helper disSqLinLin()
        
	// distance squared - between line and point
	function disSqLinPoint( line, point ) {
		var w = point.Minus(line.origin),
			prj = w.dot(line.direction);
			//vsq = line.direction.dot(line.direction),  // = 1 as line has unit dir vector
		
		// w^2 - (prj^2)/vsq ( from pythagorian a^2 + b^2 = c^2 )            
		return w.dot(w) - prj*prj; // ( 2nd term since vsq = 1 anyway!)
		
	} // disSqLinPoint()
	
	// friends methods
	var friends = {
	
		distanceSquared: function(e0, e1) {
			// 1. between 2 lines
			// 2. between a line and a point
			
			if ( e0.isClass(module.Lin3) && e1.isClass(module.Lin3) ) {
				// between 2 lines
				return disSqLinLin(e0, e1);
			}
			else if ( e0.isClass(module.Lin3) && e1.isClass(module.Point3) ) {
				return disSqLinPoint(e0, e1);
			}
			else if (e0.isClass(module.Point3) && e1.isClass(module.Lin3) ) {
				return disSqLinPoint(e1, e0);
			}
			
			// unknown parameter types
			return null;
		},
		
        // get closest points between 2 lines
        ClosestPoints: function(l1, l2) {
            var w0 = l1.origin.Minus(l2.origin),
				a = l1.direction.dot(l2.direction),	// u.v
				// b = l2.direction.dot(l2.direction), should always be 1 since L2d is the unit direction vector
				// c = l1.direction.dot(l1.direction), should always be 1 since L1d is the unit direction vector
				d = w0.dot(l2.direction),			// w0.v
				e = w0.dot(l1.direction),			// w0.u
				denom = a*a - 1.0;		// denom = a*a-b*c; but since  b=c=1, simplify to denom = a*a-1;
				
            if ( module.Includes.isZero(denom) ) {
				// s = 0.0
				// t= d/b, but since b=1, we simplify to t = d
				return {
                	Pc: module.Vec3(l1.origin),								// L1o
                	Qc: l2.origin.Plus(l2.direction.ScaleUp(d))	// L2o + t*L2d
				};
            } else
            {
				// s = ((e*b-a*d)/denom), but since b=1; we simplify to s = ((e-a*d)/denom)
				// t = ((e*a-d*c)/denom), but since c=1; we simplify to t = ((e*a-d)/denom)
				return {
                	Pc: l1.direction.ScaleUp((e-a*d)/denom).plus(l1.origin),	// L1o + s*L1d
                	Qc: l2.direction.ScaleUp((e*a-d)/denom).plus(l2.origin)		// L2o + t*L2d
				};
            }
        }

    }; //friends{}

	
    for (var f in friends) {
        friends.hasOwnProperty(f) && (module.Lin3[f] = friends[f]);
    };
    
} )(mLIB);	 // function(module){}
